package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author yb0os1
 * @since 2024-12-16
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {
    private final LearningLessonMapper learningLessonMapper;
    private final CourseClient courseClient;
    private final LearningRecordDelayTaskHandler learningRecordDelayTaskHandler;

    /**
     * 根据课程id查询学习记录
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //1、获取当前用户
        Long userId = UserContext.getUser();

        //2、查询课表信息
        //课表信息是在另外一个service中 为了防止循环引用的问题 我们引入mapper
        //根据courseId 和 userId 查询课表信息 唯一
        LearningLesson learningLesson = learningLessonMapper.selectOne(new LambdaQueryWrapper<LearningLesson>()
                .eq(LearningLesson::getCourseId, courseId)
                .eq(LearningLesson::getUserId, userId)
        );
        if (learningLesson == null) {
            throw new BizIllegalException("课表不存在");
        }

        //3、查询学习记录
        //一个课程 有多个小结 所以可以对应多个学习记录 一个小节（视频）对应一个学习记录
        List<LearningRecord> learningRecordList = lambdaQuery()
                .eq(LearningRecord::getLessonId, learningLesson.getId()).list();
        if (CollUtils.isEmpty(learningRecordList)) {
            log.error("不存在学习记录");
            return null;
        }

        //4、封装结果返回
        List<LearningRecordDTO> learningRecordDTOS = BeanUtils.copyList(learningRecordList, LearningRecordDTO.class);
        LearningLessonDTO res = new LearningLessonDTO();
        res.setRecords(learningRecordDTOS);
        res.setLatestSectionId(learningLesson.getLatestSectionId());
        res.setId(learningLesson.getId());
        return res;
    }

    /**
     * 提交学习记录
     *
     * @param dto
     */

    @Override
    @Transactional
    public void commitLearningRecord(LearningRecordFormDTO dto) {
        //1、获取当前用户
        Long userId = UserContext.getUser();
        //2、处理学习记录 返回该小结是否学习完
        boolean isFinish = handleStudyRecord(dto, userId);
        //3、处理课表数据 只有当第一次完成小节学习完才要处理课表数据 其他情况不需要处理课表
        if (!isFinish) {
            // 没有新学完的小节，无需更新课表中的学习进度
            return;
        }
        // TODO 学习完一个小节要新增积分哦
        handleLearningLesson(dto);
    }

    //处理课表数据
    ///改进这个方法 只有当第一次完成小节的时候才要执行
    private void handleLearningLesson(LearningRecordFormDTO dto) {
        //1、获取课表
        LearningLesson learningLesson = learningLessonMapper.selectOne(new LambdaQueryWrapper<LearningLesson>()
                .eq(LearningLesson::getId, dto.getLessonId())
        );
        if (learningLesson == null) {
            throw new BizIllegalException("课表不存在");
        }
        //2、小节是否完成学习
        boolean isAllFinish = false;
        //完成学习了的话 要把课表中的小节完成数+1 然后再判断这门课程是否完成学习
        //获取已经学习了的章节数
        Integer learnedSections = learningLesson.getLearnedSections();
        //获取总的章节数
        CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(learningLesson.getCourseId(), false, false);
        Integer sectionNum = cInfo.getSectionNum();
        //是否完成该课程的学习
        isAllFinish = learnedSections + 1 >= sectionNum;
        //只要完成了本小节的学习任务 就要更新课表中的已学习小节数量
        learningLesson.setLearnedSections(learningLesson.getLearnedSections() + 1);
        //不管完没完成学习小节 都要将课表中的最近学习的小节id和最近学习时间更新
        if (isAllFinish) {
            //完成了该课程全部小节的学习
            learningLesson.setStatus(LessonStatus.FINISHED.getValue());
        } else if (learningLesson.getStatus().equals(LessonStatus.NOT_BEGIN.getValue())) {
            //如果是未开始状态 就要变成学习中状态
            learningLesson.setStatus(LessonStatus.LEARNING.getValue());
        }
        learningLesson.setLatestLearnTime(dto.getCommitTime());
        learningLesson.setLatestSectionId(dto.getSectionId());
        int i = learningLessonMapper.updateById(learningLesson);
        if (i <= 0) {
            throw new BizIllegalException("更新课表失败");
        }
    }

    //处理学习记录
    private boolean handleStudyRecord(LearningRecordFormDTO dto, Long userId) {
        boolean isFinish = false;
        //1、判断是否存在小节的测试
        if (dto.getSectionType().equals(SectionType.EXAM)) {
            //2、小节测试流程
            handleExam(dto, userId);
            isFinish = true;
        } else if (dto.getSectionType().equals(SectionType.VIDEO)) {
            //3、仅视频流程
            isFinish = handleVideo(dto, userId);
        } else {
            throw new BizIllegalException("小节类型错误");
        }

        return isFinish;
    }

    //处理只有视频的小结的流程
    private boolean handleVideo(LearningRecordFormDTO dto, Long userId) {
        ///对应流程图：记录是否已经存在
        //1、根据lessonId 和 sectionId （可以唯一确定一个记录）查找该记录是否存在
        ////改进：先从缓存中读取，如果不存在再从数据库中读取 注意 从缓存中查找的record不存在lessonId和sectionId 从数据库中查找的才有
        ////！！保险起见 想要使用lessonId 和 sectionId是从dto获取！！
        LearningRecord record = queryOldRecord(dto.getLessonId(), dto.getSectionId());
        //2、如果学习记录不存在
        if (record == null) {
            //新增学习记录
            LearningRecord learningRecord = BeanUtils.copyBean(dto, LearningRecord.class);
            learningRecord.setUserId(userId);
            boolean isSuccess = save(learningRecord);
            if (!isSuccess) {
                throw new BizIllegalException("新增学习记录失败");
            }
            return false;//第一次保存学习记录 我们认为肯定是没有完成播放的
        }
        //3、学习记录存在
        //3.1 判断是否为第一次完成学习
        // 旧状态(这门课程是处于学习中的状态)是未完成
        // 本次播放的进度超过50%
        boolean isFinish = (!record.getFinished()) && (dto.getMoment() * 2 >= dto.getDuration());

        //3.2 不是第一次学完（没学完 多次学完）
        if (!isFinish) {
            //缓存到redis中 当缓存存在的时候这里的record是从redis读取的 lessonId和sectionId都是不存在的 但是finished id都是存在的
            record.setLessonId(dto.getLessonId());
            record.setSectionId(dto.getSectionId());
            record.setMoment(dto.getMoment());
            learningRecordDelayTaskHandler.addDelayTask(record);
            return false;
        }

        //3.2 是第一次学完 更新学习记录
        boolean success = this.lambdaUpdate()
                .set(LearningRecord::getFinished, true)
                .set(LearningRecord::getFinishTime, dto.getCommitTime())
                .set(LearningRecord::getMoment, dto.getMoment())
                .eq(LearningRecord::getId, record.getId())
                .update();
        if (!success) {
            throw new BizIllegalException("更新学习记录失败");
        }
        //3.3 更新缓存 为什么更新缓存？此时的学习记录是已完成的，但是如果缓存存在的话是未完成的 这样的话出现了缓存和数据库不一致的情况
        learningRecordDelayTaskHandler.deleteRecordCacheData(dto.getLessonId(), dto.getSectionId());
        //3.3 返回是否完成学习
        return true;
    }

    //读取旧的学习记录 先从redis中读取 不存在在查询数据库
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        //1、从缓存中读取
        LearningRecord recordCacheData = learningRecordDelayTaskHandler.readRecordCacheData(lessonId, sectionId);
        //2、缓存中是否存在
        //存在直接返回
        if (recordCacheData != null) {
            log.info("从redis中查询到的学习记录：{}", recordCacheData);
            return recordCacheData;
        }
        //处理缓存不存在的情况
        //3、从mysql数据库中查询
        //如果从数据库没有查到就返回null 查询结果没有查到也是null
        LearningRecord record = lambdaQuery().eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .one();
        if (record==null){
            return null;
        }
        //4、写入缓存
        learningRecordDelayTaskHandler.writeRecordCache(record);
        log.info("从数据库中查询到的学习记录：{}", record);
        return record;
    }

    //处理存在考试的流程
    private void handleExam(LearningRecordFormDTO dto, Long userId) {
        //新增学习记录
        LearningRecord learningRecord = BeanUtils.copyBean(dto, LearningRecord.class);
        learningRecord.setUserId(userId);
        learningRecord.setFinished(true);
        learningRecord.setFinishTime(dto.getCommitTime());

        boolean isSuccess = save(learningRecord);
        if (!isSuccess) {
            throw new BizIllegalException("新增学习记录失败");
        }
    }
}
